import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_bmflocation/flutter_bmflocation.dart';

import '../config/map_config.config.dart';
import '../models/map_location.model.dart';
import '../services/map_location_service.service.dart';
import '../services/map_service.service.dart';
import '../services/navigator_helper.service.dart';
import '../services/user_interaction.service.dart';
import './general_search_delegate.widget.dart';

class MapLocationSelector extends StatefulWidget {
  const MapLocationSelector(this.location,
      {super.key, this.coordType = BMFLocationCoordType.gcj02});

  final MapLocation location;
  final BMFLocationCoordType coordType;

  @override
  State<MapLocationSelector> createState() => _MapLocationSelectorState();
}

class _MapLocationSelectorState extends State<MapLocationSelector> {
  // For displaying bottom sheet for user interaction.
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  // For locating user position.
  late final MapLocationService _mapLocationService = MapLocationService(
      locationChanged: _onLcationChanged, coordType: widget.coordType);

  // For toast and bottom sheet usage.
  late final UserInteraction _userInteraction =
      UserInteraction(context: context);

  // Indicates if currently positioning.
  bool _isPositioning = false;

  late MapLocation _location = widget.location;

  BMFMapController? _bmfMapController;

  // For displaying bottom sheet for user interaction.
  PersistentBottomSheetController? _bottomSheetController;

  @override
  void dispose() {
    _mapLocationService.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: _buildAppBar(),
      floatingActionButton: _buildFloatingActionButton(),
      body: _buildMap(),
    );
  }

  /// Builds app bar.
  AppBar _buildAppBar() {
    return AppBar(
      title: Text(_isPositioning ? '正在定位...' : '可点击地图选择'),
      actions: <Widget>[
        IconButton(
          icon: const Icon(Icons.search),
          onPressed: _query,
        ),
      ],
    );
  }

  /// Builds floating action button.
  Widget _buildFloatingActionButton() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      children: <Widget>[
        FloatingActionButton(
          onPressed: !_isPositioning
              ? () {
                  if (mounted) {
                    setState(() => _isPositioning = true);
                  }

                  _mapLocationService.startLocation();
                }
              : null,
          child: const Icon(Icons.gps_fixed),
        ),
        const SizedBox(height: 60.0),
      ],
    );
  }

  /// Builds map widget.
  Widget _buildMap() {
    return BMFMapWidget(
      onBMFMapCreated: _onBMFMapCreated,
      mapOptions: _getMapOptions(),
    );
  }

  Widget _buildSearchResultItem(MapLocation location) {
    return ListTile(
      leading: const Icon(
        Icons.location_on,
        size: 30.0,
      ),
      title: Text(location.name!),
      subtitle: Text('${location.city}${location.address}'),
    );
  }

  Future<void> _query() async {
    final location = await showSearch<dynamic>(
      context: context,
      delegate: GeneralSearchDelegate(
        _queryPoiByKeyword,
        buildResultItem: (entry) => _buildSearchResultItem(entry),
      ),
    );

    if (location != null) {
      _onLcationChanged(location);
    }
  }

  Future<List<MapLocation>> _queryPoiByKeyword(String keyword) async {
    return MapService.searchPoi(keyword, _location.city ?? _location.province);
  }

  /// Sets up callbacks once map has been created.
  void _onBMFMapCreated(BMFMapController controller) {
    _bmfMapController = controller;

    _bmfMapController!.setMapOnClickedMapPoiCallback(
        callback: (poi) => _showLocationInfoByCoordinates(
            poi.pt!.latitude, poi.pt!.longitude));

    _bmfMapController!.setMapOnClickedMapBlankCallback(
        callback: (coordinate) => _showLocationInfoByCoordinates(
            coordinate.latitude, coordinate.longitude));

    _bmfMapController!.setMapDidLoadCallback(callback: _initLoad);
  }

  /// Shows input coordiate or starts locating once map has been loaded.
  void _initLoad() {
    if (_location.latitude != null && _location.longitude != null) {
      // Moves carmera to this coordinate.
      _bmfMapController!.setCenterCoordinate(
          BMFCoordinate(_location.latitude!, _location.longitude!), true);

      _showLocationInfo(_location);
    } else {
      /// If no input coordinates we need to start positioning.
      if (mounted) {
        setState(() => _isPositioning = true);
      }

      _mapLocationService.startLocation();
    }
  }

  /// Adds marker and shows poi info based on coordinates.
  void _showLocationInfo(MapLocation location) {
    _location = location;

    // Sets the coordinate as marker.
    _bmfMapController!.cleanAllMarkers();

    final selectedCoordinate =
        BMFCoordinate(_location.latitude!, _location.longitude!);

    _bmfMapController!.addMarker(
      BMFMarker.icon(
          position: selectedCoordinate, icon: MapConfig.markerIconName),
    );

    // IMPORTANT!!!
    // Must use class members instead of parameter of [_showLocationInfo].
    // Displays formatted address on bottom sheet.
    if (_bottomSheetController == null) {
      _bottomSheetController = _scaffoldKey.currentState?.showBottomSheet(
        (context) => _userInteraction.getSelectionBottomSheetContent([
          Expanded(child: Text(_location.formattedAddress!)),
          TextButton(
            child: const Text(
              '确定',
              style: TextStyle(color: Colors.blue),
            ),
            onPressed: () {
              _bottomSheetController!.close();
              NavigatorHelper.pop(context, _location);
            },
          ),
        ]),
      );

      // When bottom sheet is closed then we need to clear it.
      _bottomSheetController!.closed
          .whenComplete(() => _bottomSheetController = null);
    } else {
      // If bottom sheet already opens we only need to refresh its content.
      _bottomSheetController?.setState!(() {});
    }
  }

  /// Adds marker and shows poi info based on coordinates.
  Future<void> _showLocationInfoByCoordinates(
      double latitude, double longitude) async {
    final location = await MapService.searchReverseGeoCode(
        BMFCoordinate(latitude, longitude));

    _showLocationInfo(location);
  }

  /// Gets map options.
  BMFMapOptions _getMapOptions() {
    return BMFMapOptions(
      zoomLevel: 18,
      rotateEnabled: false,
      showZoomControl: false,
    );
  }

  /// Listens on location change event and moves camera.
  Future<void> _onLcationChanged(MapLocation location) async {
    // Just in case map is not created yet.
    while (_bmfMapController == null) {
      await Future.delayed(const Duration(milliseconds: 10));
    }

    // Moves carmera to this coordinate.
    _bmfMapController!.setCenterCoordinate(
        BMFCoordinate(location.latitude!, location.longitude!), true);

    _showLocationInfo(location);

    if (mounted) {
      setState(() => _isPositioning = false);
    }
  }
}
